/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

/*
  1. Promise 就是一个类 在执行这个类的时候 需要传递一个执行器进去 执行器会立即执行
  2. Promise 中有三种状态 分别为 成功 fulfilled 失败 rejected 等待 pending
    pending -> fulfilled
    pending -> rejected
    一旦状态确定就不可更改
  3. resolve和reject函数是用来更改状态的
    resolve: fulfilled
    reject: rejected
  4. then方法内部做的事情就判断状态 如果状态是成功 调用成功的回调函数 如果状态是失败 调用失败回调函数 then方法是被定义在原型对象中的
  5. then成功回调有一个参数 表示成功之后的值 then失败回调有一个参数 表示失败后的原因
  6. 同一个promise对象下面的then方法是可以被调用多次的
  7. then方法是可以被链式调用的, 后面then方法的回调函数拿到值的是上一个then方法的回调函数的返回值
*/

const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败

class MyPromise {
    constructor(exefn) {
      try {
        exefn(this.resolve, this.reject)
      } catch (error) {
        this.reject(error)
      }
    }
  
    status = PENDING
  
    value = undefined
    reason = undefined
  
    successCallback = []
    failCallback = []
  
    resolve = value => {
      // status 不是PENDING，则不再执行
      if (this.status !== PENDING) return;
  
      // 改变状态为 fulfilled
      this.status = FULFILLED;
  
      // 保存执行器成功时传入的值
      this.value = value;
  
      // 兼顾同步。所以先判断 successCallback 是否存在，存在则执行
      // this.successCallback && this.successCallback(this.value)
  
      // 执行其中有异步时，依次执行所有 then 的成功回调
      while (this.successCallback.length) {
        // shift 会取得数组中的第一个元素并返回，且改变原数组
        // 但 this.successCallback.length 为 0 时，结束 while 循环
        this.successCallback.shift()()
      }
    }
    reject = reason => {
      // status 不是PENDING，则不再执行
      if (this.status !== PENDING) return;
  
      // 改变状态为 rejected
      this.status = REJECTED;
  
      // 保存执行器失败时传入的错误原因
      this.reason = reason;
  
      // 兼顾同步。所以先判断 failCallback 是否存在，存在则执行
      // this.failCallback && this.failCallback(this.reason)
  
      // 执行其中有异步时，依次执行所有 then 的失败回调
      while (this.failCallback.length) {
        // shift 会取得数组中的第一个元素并返回，且改变原数组
        // 但 this.failCallback.length 为 0 时，结束 while 循环
        this.failCallback.shift()()
      }
    }
  
    then(successCallback, failCallback) {
      // 处理 then 的参数为可选的情况
      // 这里的 value、reason 为形参
      successCallback = successCallback ? successCallback : value => value;
      failCallback = failCallback ? failCallback : reason => { throw reason };
  
      let returnPromise = new MyPromise((resolve, reject) => {
  
        // 判断 status 状态，来决定调用的回调
        if (this.status === FULFILLED) {
          /**
           * 直接在 judgeCallbackReturn 中传递 returnPromise 是有问题的
           * returnPromise 需要在 new MyPromise() 完成后在有值，所以这里使用 setTimeout 异步一下
           */
          setTimeout(() => {
            try {
              let successReturn = successCallback(this.value);
              judgeCallbackReturn(returnPromise, successReturn, resolve, reject);
            } catch (error) {
              reject(error)
            }
          }, 0);
  
        } else if (this.status === REJECTED) {
          setTimeout(() => {
            try {
              let failReturn = failCallback(this.reason);
              judgeCallbackReturn(returnPromise, failReturn, resolve, reject);
            } catch (error) {
              reject(error)
            }
          }, 0);
        } else {
          // 执行器中有异步，status 仍为 pending 时
          // 对于同一 MyPromise 实例多次使用 then，在 pending 时，将回调存入相应的暂存回调数组中，以保证每一个回调都会在后续中执行
  
          // this.successCallback.push(successCallback);
          // this.failCallback.push(failCallback);
  
          // 因为 then 的链式调用需要用 resolve，所以暂存回调数组中的回调函数改成一下形式
          this.successCallback.push(() => {
            setTimeout(() => {
              try {
                let successReturn = successCallback(this.value);
                judgeCallbackReturn(returnPromise, successReturn, resolve, reject);
              } catch (error) {
                reject(error)
              }
            }, 0);
          });
          this.failCallback.push(() => {
            setTimeout(() => {
              try {
                let failReturn = failCallback(this.reason);
                judgeCallbackReturn(returnPromise, failReturn, resolve, reject);
              } catch (error) {
                reject(error)
              }
            }, 0);
          });
        }
      })
  
      return returnPromise;
    }
  
    finally(callback) {
      // 使用 then 得到当前 MyPromise 的状态
      // 在 then 的 resolve/reject 中调用 callback 则无论 MyPromise 是否成功, 都会执行
      // then 会返回一个 MyPromise 对象，所以 return 后，finally 后面就可以再接 then
      return this.then(res => {
        // 在 finally 后调用then，该 then 会拿到 finally 返回的 MyPromise 的结果，所以这里要 return res
        // callback();
        // return res
  
        // 若 callback 有异步，则用 MyPromise.resolve() 把 callback 变成 MyPromise，
        // 然后在其后面调用 then，并在其成功回调中返回 res
        // 这样就可以确保 callback 执行完毕后再返回当前 MyPromise 的结果
        return MyPromise.resolve(callback()).then(() => res)
      }, err => {
        // 失败时，需要 throw err，如此才能将错误信息传递给 finally 后面调用的 then
        // callback();
        // throw err
  
        // 用 MyPromise.resolve() 把 callback 变成 MyPromise，调用 then，并在其成功回调中 throw err 来传递错误
        return MyPromise.resolve(callback()).then(() => {throw err})
      })
    }
  
    catch(failCallback) {
      // 使用 then 得到当前 MyPromise 的状态，若失败，则执行 catch 的回调
      // 使用 return 返回 MyPromise 对象，以便 catch 后面链式调用 MyPromise 其他方法
      return this.then(undefined, failCallback)
    }
  
    static all(array) {
      let results = []; // 存储 array 中每项的结果
      let index = 0; // 记数处理完成的个数
  
      // 返回 MyPromise 对象
      return new MyPromise((resolve, reject) => {
        function addResult(key,value) {
          results[key] = value;
          index++;
  
          if(index === array.length) {
            resolve(results)
          }
        }
  
        // 循环获取 array 中的每一项
        for (let i = 0; i < array.length; i++) {
          let current = array[i];
          // 判断 current 是 普通值 还是 MyPromise 对象
          if (current instanceof MyPromise) {
            // 因为 current 作为 MyPromise 对象，其中可能存在异步的情况
            // 而 for 循环瞬间完成，current.then 的回调还来不及执行，这会造成 result 中有空值
  
            // 为解决异步问题，这里在 all 中添加一个 index 变量作为记数，定义 addResult(key,value) 函数作为将结果保存进 results 的操作
            // addResult 没执行一次，index 加一， 当 index 等于 array.length 时，代表所有项已经执行完成，此时调用 resolve
            current.then(res => { addResult(i, res) }, reject)
          } else {
            addResult(i, current)
          }
        }
      })
    }
  
    static resolve(value) {
      // 判断是否为 MyPromise 对象
      // 是，则直接返回
      if(value instanceof MyPromise) return value;
  
      // 否，返回一个成功的 MyPromise
      return new MyPromise(resolve => resolve(value))
    }
  }
  
  /**
   * 新建函数来统一处理对返回值的判断
   * 
   * 1、判断 returnValue 是否为 returnPromise，是则直接抛出异常
   * 2、判断 returnValue 是否为 MyPromise 对象
   *    是，则使用 then 判断 returnValue 的状态，回调为 returnPromise 的 resolve, reject
   *    否，则使用 resolve
   */
  function judgeCallbackReturn(returnPromise, returnValue, resolve, reject) {
    if (returnValue === returnPromise) {
      return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    if (returnValue instanceof MyPromise) {
      returnValue.then(resolve, reject)
    } else {
      resolve(returnValue)
    }
  
  }
  
  module.exports = MyPromise